/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Reflection;

using Microsoft.Scripting.Generation;
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Utils;

namespace Microsoft.Scripting.Ast {
    //CONFORMING
    /// <summary>
    /// This captures a block of code that should correspond to a .NET method body.  It takes
    /// input through parameters and is expected to be fully bound.  This code can then be
    /// generated in a variety of ways.  The variables can be kept as .NET locals or in a
    /// 1st class environment object. This is the primary unit used for passing around
    /// Expression Trees in the DLR.
    /// </summary>
    public class LambdaExpression : Expression {
        private readonly Type _returnType;
        private readonly string _name;
        private readonly Expression _body;
        private readonly ReadOnlyCollection<ParameterExpression> _parameters;

        // TODO: Evaluate necessity...
        private readonly bool _parameterArray;

        internal LambdaExpression(
            Annotations annotations,
            ExpressionType nodeType,
            Type delegateType,
            string name,
            Type returnType,
            Expression body,
            ReadOnlyCollection<ParameterExpression> parameters,
            bool parameterArray
        )
            : base(annotations, nodeType, delegateType) {

            Assert.NotNull(returnType);

            _name = name;
            _returnType = returnType;
            _body = body;
            _parameters = parameters;
            _parameterArray = parameterArray;
        }

        public Type ReturnType {
            get { return _returnType; }
        }

        public ReadOnlyCollection<ParameterExpression> Parameters {
            get { return _parameters; }
        }

        public string Name {
            get { return _name; }
        }

        // TODO: remove?
        public bool ParameterArray {
            get { return _parameterArray; }
        }

        public Expression Body {
            get { return _body; }
        }

        internal override void BuildString(StringBuilder builder) {
            ContractUtils.RequiresNotNull(builder, "builder");

            if (Parameters.Count == 1) {
                Parameters[0].BuildString(builder);
            } else {
                builder.Append("(");
                for (int i = 0, n = Parameters.Count; i < n; i++) {
                    if (i > 0)
                        builder.Append(", ");
                    Parameters[i].BuildString(builder);
                }
                builder.Append(")");
            }
            builder.Append(" => ");
            _body.BuildString(builder);
        }

        public Delegate Compile() {
            return LambdaCompiler.CompileLambda(this);
        }
    }
    
    //CONFORMING
    public sealed class Expression<TDelegate> : LambdaExpression {
        internal Expression(
            Annotations annotations,
            ExpressionType nodeType,
            string name,
            Type returnType,
            Expression body,
            ReadOnlyCollection<ParameterExpression> parameters,
            bool parameterArray
        )
            : base(annotations, nodeType, typeof(TDelegate), name, returnType, body, parameters, parameterArray) {
        }

        public new TDelegate Compile() {
            return LambdaCompiler.CompileLambda<TDelegate>(this);
        }
    }


    public partial class Expression {
        //CONFORMING
        //TODO: need overloads with annotations and other params
        public static Expression<TDelegate> Lambda<TDelegate>(Expression body, params ParameterExpression[] parameters) {
            return Lambda<TDelegate>(body, (IEnumerable<ParameterExpression>)parameters);
        }

        public static Expression<TDelegate> Lambda<TDelegate>(Expression body, IEnumerable<ParameterExpression> parameters) {
            ContractUtils.RequiresNotNull(body, "body");

            ReadOnlyCollection<ParameterExpression> parameterList = CollectionUtils.ToReadOnlyCollection(parameters);
            ContractUtils.RequiresNotNullItems(parameterList, "parameters");

            Expression<TDelegate> lambda = new Expression<TDelegate>(Annotations.Empty, ExpressionType.Lambda, "lambda_method", body.Type, body, parameterList, false);
            ValidateDelegateType(lambda, typeof(TDelegate));

            return lambda;
        }


        public static LambdaExpression Lambda(Expression body, params ParameterExpression[] parameters) {
            return Lambda(body, (IEnumerable<ParameterExpression>)parameters);
        }

        public static LambdaExpression Lambda(Expression body, IEnumerable<ParameterExpression> parameters) {
            return Lambda(body, "lambda_method", body.Type, parameters);
        }

        //CONFORMING
        public static LambdaExpression Lambda(Type delegateType, Expression body, params ParameterExpression[] parameters) {
            return Lambda(delegateType, body, "lambda_method", body.Type, parameters);
        }

        public static LambdaExpression Lambda(Expression body, string name, Type returnType, IEnumerable<ParameterExpression> parameters) {
            return Lambda(body, name, returnType, Annotations.Empty, parameters);
        }

        public static LambdaExpression Lambda(Type delegateType, Expression body, string name, Type returnType, IEnumerable<ParameterExpression> parameters) {
            return Lambda(delegateType, body, name, returnType, Annotations.Empty, parameters);
        }

        public static LambdaExpression Lambda(Type delegateType, Expression body, string name, Type returnType, Annotations annotations, IEnumerable<ParameterExpression> parameters) {
            return Lambda(delegateType, body, name, returnType, false, annotations, parameters);
        }

        public static LambdaExpression Lambda(Expression body, string name, Type returnType, Annotations annotations, IEnumerable<ParameterExpression> parameters) {

            ContractUtils.RequiresNotNull(name, "name");
            ContractUtils.RequiresNotNull(returnType, "returnType");
            ContractUtils.RequiresNotNull(body, "body");

            ReadOnlyCollection<ParameterExpression> parameterList = CollectionUtils.ToReadOnlyCollection(parameters);
            ContractUtils.RequiresNotNullItems(parameterList, "parameters");

            Type delegateType = GetDelegateTypeForSignature(parameterList, returnType);
            return new LambdaExpression(annotations, ExpressionType.Lambda, delegateType, name, returnType, body, parameterList, false);
        }

        public static LambdaExpression Lambda(Type delegateType, Expression body, string name, Type returnType, bool parameterArray, Annotations annotations, IEnumerable<ParameterExpression> parameters) {

            ContractUtils.RequiresNotNull(name, "name");
            ContractUtils.RequiresNotNull(delegateType, "delegateType");
            ContractUtils.RequiresNotNull(returnType, "returnType");
            ContractUtils.RequiresNotNull(body, "body");

            ReadOnlyCollection<ParameterExpression> parameterList = CollectionUtils.ToReadOnlyCollection(parameters);
            ContractUtils.RequiresNotNullItems(parameterList, "parameters");
            
            LambdaExpression lambda = new LambdaExpression(annotations, ExpressionType.Lambda, delegateType, name, returnType, body, parameterList, parameterArray);

            ValidateDelegateType(lambda, delegateType);

            return lambda;
        }

        public static LambdaExpression Generator(Type delegateType, string name, Expression body, params ParameterExpression[] parameters) {
            return Generator(delegateType, name, body, Annotations.Empty, parameters);
        }

        public static LambdaExpression Generator(Type delegateType, string name, Expression body, Annotations annotations, params ParameterExpression[] parameters) {
            ContractUtils.RequiresNotNull(name, "name");
            ContractUtils.RequiresNotNull(delegateType, "delegateType");
            ContractUtils.RequiresNotNull(body, "body");
            ContractUtils.RequiresNotNullItems(parameters, "parameters");

            LambdaExpression lambda = new LambdaExpression(
                annotations,
                ExpressionType.Generator,
                delegateType,
                name,
                typeof(IEnumerator),
                body,
                CollectionUtils.ToReadOnlyCollection(parameters),
                false
            );

            ValidateDelegateType(lambda, delegateType);

            return lambda;
        }

        /// <summary>
        /// Extracts the signature of the LambdaExpression as an array of Types
        /// </summary>
        internal static Type[] GetLambdaSignature(LambdaExpression lambda) {
            Debug.Assert(lambda != null);

            ReadOnlyCollection<ParameterExpression> parameters = lambda.Parameters;
            Type[] result = new Type[parameters.Count];
            for (int i = 0; i < parameters.Count; i++) {
                result[i] = parameters[i].Type;
            }
            return result;
        }
    
        /// <summary>
        /// Validates that the delegate type of the lambda
        /// matches the lambda itself.
        /// 
        /// * Return types of the lambda and the delegate must be identical.
        /// 
        /// * Without parameter array on the delegate type, the signatures must
        ///   match perfectly as to count and types of parameters.
        ///   
        /// * With parameter array on the delegate type, the common subset of
        ///   parameters must match
        /// </summary>
        private static void ValidateDelegateType(LambdaExpression lambda, Type delegateType) {
            ContractUtils.Requires(delegateType != typeof(Delegate), "type", "type must not be System.Delegate.");
            ContractUtils.Requires(TypeUtils.CanAssign(typeof(Delegate), delegateType), "type", "Incorrect delegate type.");

            MethodInfo mi = delegateType.GetMethod("Invoke");
            ContractUtils.RequiresNotNull(mi, "Delegate must have an 'Invoke' method");

            ContractUtils.Requires(mi.ReturnType == lambda.ReturnType, "type", "Delegate type doesn't match LambdaExpression");

            ParameterInfo[] infos = mi.GetParameters();
            ReadOnlyCollection<ParameterExpression> parameters = lambda.Parameters;

            if (infos.Length > 0 && CompilerHelpers.IsParamArray(infos[infos.Length - 1])) {
                ContractUtils.Requires(infos.Length - 1 <= parameters.Count, "Delegate and lambda parameter count mismatch");

                // Parameter array case. The lambda may have more parameters than delegate,
                // and can also have parameter array as its last parameter, however all of the
                // parameters upto delegate's parameter array (excluding) must be identical

                ValidateIdenticalParameters(infos, parameters, infos.Length - 1);
            } else {
                ContractUtils.Requires(infos.Length == parameters.Count, "Delegate and lambda parameter count mismatch");

                // No parameter array. The lambda must have identical signature to that of the
                // delegate, and it may not be marked as parameter array itself.
                ValidateIdenticalParameters(infos, parameters, infos.Length);

                ContractUtils.Requires(!lambda.ParameterArray, "lambda", "Parameter array delegate type required for parameter array lambda");
            }
        }

        private static void ValidateIdenticalParameters(ParameterInfo[] infos, ReadOnlyCollection<ParameterExpression> parameters, int count) {
            Debug.Assert(count <= infos.Length && count <= parameters.Count);
            while (count-- > 0) {
                ContractUtils.Requires(infos[count].ParameterType == parameters[count].Type, "type");
            }
        }

        //CONFORMING
        private static Type GetDelegateTypeForSignature(IList<ParameterExpression> parameters, Type returnType) {
            ContractUtils.RequiresNotNull(returnType, "returnType");

            bool action = returnType == typeof(void);

            int paramCount = parameters == null ? 0 : parameters.Count;
            Type[] typeArgs = new Type[paramCount + (action ? 0 : 1)];
            for (int i = 0; i < paramCount; i++) {
                ContractUtils.RequiresNotNull(parameters[i], "parameter");
                typeArgs[i] = parameters[i].Type;
            }

            Type type;
            if (action)
                type = GetActionType(typeArgs);
            else {
                typeArgs[paramCount] = returnType;
                type = GetFuncType(typeArgs);
            }
            return type;
        }
        //CONFORMING
        public static Type GetFuncType(params Type[] typeArgs) {
            ContractUtils.RequiresNotNull(typeArgs, "typeArgs");
            ContractUtils.Requires(typeArgs.Length > 0, "incorrect number of type arguments for Func.");

            Type funcType;

            switch (typeArgs.Length) {
                case 1:
                    funcType = typeof(Utils.Function<>).MakeGenericType(typeArgs);
                    break;
                case 2:
                    funcType = typeof(Utils.Function<,>).MakeGenericType(typeArgs);
                    break;
                case 3:
                    funcType = typeof(Utils.Function<,,>).MakeGenericType(typeArgs);
                    break;
                case 4:
                    funcType = typeof(Utils.Function<,,,>).MakeGenericType(typeArgs);
                    break;
                case 5:
                    funcType = typeof(Utils.Function<,,,,>).MakeGenericType(typeArgs);
                    break;
                default:
                    throw new ArgumentException("incorrect number of type arguments for Func.", "typeArgs"); ;
            }
            return funcType;
        }
        //CONFORMING
        public static Type GetActionType(params Type[] typeArgs) {
            ContractUtils.RequiresNotNull(typeArgs, "typeArgs");

            Type actionType;

            switch (typeArgs.Length) {
                case 0:
                    actionType = typeof(Utils.Action);
                    break;
                case 1:
                    actionType = typeof(System.Action<>).MakeGenericType(typeArgs);
                    break;
                case 2:
                    actionType = typeof(Utils.Action<,>).MakeGenericType(typeArgs);
                    break;
                case 3:
                    actionType = typeof(Utils.Action<,,>).MakeGenericType(typeArgs);
                    break;
                case 4:
                    actionType = typeof(Utils.Action<,,,>).MakeGenericType(typeArgs);
                    break;
                default:
                    throw new ArgumentException("incorrect number of type arguments for Action.", "typeArgs"); ;
            }
            return actionType;
        }
    }
}
